from nest.tree.cover import CoveringTree
from nest.tree.exception import PathError, TreeError, TreeValueError
from nest.utils import isbuiltin


class DataTree(CoveringTree):

    @property
    def default_value(self):
        return dict()

    def get_item(self, key):
        data = self.value
        data_type = type(data)
        if data_type is list:
            key = int(key)
            if key < 0 or key > len(data):
                raise PathError()
        elif data_type is dict:
            if key not in data:
                raise PathError()
        elif data is None:
            raise PathError()
        elif not getattr(data, key, False):
            raise TreeValueError('Unsupported data type: %s.' % data_type)
        return data[key]

    def set_item(self, key, value):
        data = self.value
        data_type = type(data)
        if data_type is list:
            key = int(key)
            if key < 0 or key > len(data) + 1:
                raise TreeError('Tree item (list) assignment index is out of bounds.')
            if key > len(data):
                data.append(value)
            else:
                data[key] = value
        elif data_type is dict:
            data[key] = value
        else:
            try:
                setattr(data, key, value)
            except AttributeError:
                raise TreeValueError('Unsupported data type: %s.' % data_type)
        return data[key]

    def keys(self):
        data = self.value
        data_type = type(data)
        if data_type is list:
            return range(len(data))
        elif data_type is dict:
            return data.keys()
        if isbuiltin(data) and data_type is not object:
            # We return an empty list, since this node is obviously a leaf.
            return list()
        # Assume data is a new-object instance.
        keys = data.__dict__.keys() + type(data).__dict__.keys()
        return [
            key for key in keys
            if not key.startswith('__') and not key.endswith('__')
        ]
